18 中间件与CSRF跨站请求伪造

468次阅读
没有评论

共计 8554 个字符,预计需要花费 22 分钟才能阅读完成。

引入

1. 什么是中间件

中间件是一个很大的概念, 它介于两个事务之间

  • 服务器中间件:服务器的调优,例:Java 的 Tomcat
  • 消息队列中间件:消息队列,在应用程序与应用程序之间,
  • 数据库中间件:应用程序与数据库之间

一.Django 中间件 (middleware)

1. 什么 Django 中间件

  • 请求来的时候需要先经过中间件才能到真正的 Django 后端
  • 响应走的时候也需要经过中间件才能发送出去
  • 通俗的讲 : 中间件相当于是 Django 的门户, 你进来时要经过它, 出去的时候也要经过它
  • 介于 request 与 response 处理之间的一道处理过程, 并且在全局上改变 django 的输入与输出

2.Django 自带的中间件

  • Django 自带的中间件有七个, 在 setting.py 配置文件中, 每个中间件其实就是一个类
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',  # 处理 session
    'django.middleware.common.CommonMiddleware',  # 处理路由匹配是否带斜杠
    'django.middleware.csrf.CsrfViewMiddleware',  # 跨站请求伪造处理
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

3. 中间件的作用

  • 既然请求来和响应走都经过中间件, 那么我们就可以对所有的请求做一些预处理, 对所有返回的响应也可以做处理

18 中间件与 CSRF 跨站请求伪造

  • 每一个中间件都有具体的功能

4. 中间件的主要方法介绍

  • 可以通过查看 Django 自带中间件的源码查看他们一般都有什么方法 : (五种)
中间件方法 描述
process_request(self,request)(常用) 所有请求来时都会运行的方法
process_view(self, request, callback, callback_args, callback_kwargs) 所有路由匹配成功之后,跳转执行视图函数之前都会运行该方法
process_exception(self, request, exception) 所有视图中有异常发生时运行的方法
process_response(self, request, response)(常用) 所有返回页面响应时运行的方法
process_template_response(self,request,response) 返回的 HttpResponse 对象具有 render 属性时才会触发该方法

ps : 并不是每个中间件都有这五个方法, 只是需要什么方法就有什么方法

  • process_request(), process_response() 重点

process_request
    1. 请求来的时候会依此走 MIDDLEWARE 中定义的该方法, 如果中间件没有定义就跳过
        - 执行顺序:  MIDDLEWARE 中从上到下
    2. 如果返回 HttpResponse 对象, 请求将不会继续往后执行, 而是原路返回
        - 目的: 用来做全局相关的所有限制功能
        - 应用: 校验失败不允许访问

process_response
    1. 响应走的时候会依此走 MIDDLEWARE 中定义的该方法, 如果中间件没有定义就跳过
        - 执行顺序:  MIDDLEWARE 中从下到上
    2. 该方法必须返回  HttpResponse 对象
        - 默认返回形参 response
        - 返回自定义的
  • process_view(), process_template_response(), process_exception() 了解

process_view
    - 路由匹配之前或者执行视图函数之前时触发
    - 执行顺序: MIDDLEWARE 中从上到下
process_template_response
    - 视图函数返回结果中含有 render 属性是触发
    - 执行顺序: MIDDLEWARE 中从下到上
process_exception
    - 视图函数出现异常时触发
    - 执行顺序: MIDDLEWARE 中从下到上

5. 自定义中间件流程

  • 首先要知道中间件的执行顺序
1. 请求来时, 自上而下顺序执行
2. 响应走时, 自下而上顺序执行

18 中间件与 CSRF 跨站请求伪造

  • 自定义中间件步骤
1. 先在项目名目录或者应用名目录下创建一个任意名字的文件夹(最好见名知意)
2. 在该文件夹下创建任意名字的 py 文件(最好见名知意)
3. 在该 py 文件内书写类, 在类中书写中间件的五种方法
    - 类必须继承 MiddlewareMixin, 五种方法并不是全都要写, 需要几种写几种
4. 将类的路径以字符串的形式注册到 settings.py 配置文件中的 MIDDLEWARE 下(与 app 注册原理类似)

6. 自定义中间件操作

  • 创建文件夹和 py 文件, 再书写类

18 中间件与 CSRF 跨站请求伪造

from django.utils.deprecation import MiddlewareMixin  # 导入 Middleware 类
from django.shortcuts import HttpResponse, redirect, render

class FirstMiddleware(MiddlewareMixin):
    def process_request(self, request):
        print(" 请求来了 11111")

    def process_response(self, request, response):
        print(" 响应走了 11111")
        return response  # 必须返回 response


class SecondMiddleware(MiddlewareMixin):
    def process_request(self,request):
        print(" 请求来了 22222")

    def process_response(self,request,response):
        print(" 响应走了 22222")
        return response  # 必须返回 response
  • 到 setting.py 中的 MIDDLEWARE 配置中去注册
# 一字符串的形式添加自定义中间件的路径
'app01.middleware.mymiddle.FirstMiddleware',
'app01.middleware.mymiddle.SecondMiddleware',

注意上面两个中间件的顺序是 First 在上, Second 在下

  • 启动项目发起任意请求查看输出

18 中间件与 CSRF 跨站请求伪造

  • 如果我们在 First 中的 process_request 方法中添加 返回值 HttpResponse 对象
class FirstMiddleware(MiddlewareMixin):
    def process_request(self, request):
        print(" 请求来了 11111")
        return HttpResponse('1')  # 添加 HttpResponse 返回值
    ....
  • 再查看实验效果

18 中间件与 CSRF 跨站请求伪造

First 中 process_request 有返回 HttpResponse 对象, 那么接下来的中间件都不执行, 直接原路返回

7. 自定义中间件示例

  • 统计所有登入用户的用户名、ip、时间、平台、客户端类型

  • 中间件 mymiddle.py 文件

from django.utils.deprecation import MiddlewareMixin
from app01 import models

class MyMiddleware(MiddlewareMixin):
    def process_request(self, request):
        name = request.POST.get('name')
        if name is not None:
            IP = request.META.get('REMOTE_ADDR')
            OS = request.META.get('OS').split('_')[0]
            client = request.META.get('HTTP_USER_AGENT').split('')[-1].split('/')[0]
            if client == 'Safari':
                client = 'Chrome'
            models.Visits.objects.create(name=name, IP=IP, OS=OS, client=client)

    def process_response(self, request, response):

        return response

    def process_view(self, request, view_name, *args, **kwargs):
        print("")
  • 在 setting.py 中注册
'app01.middleware.mymiddle.MyMiddleware',
  • 路由层 urls.py 文件
# 访问统计
path('', views.visit_login),
re_path('^visit_data/', views.visit_data),
  • 视图层 views.py 文件
# 访问统计登入页面
def visit_login(request):
    if request.method == "GET":
        return render(request, 'visit_login.html')
    elif request.method == "POST":
        return render(request, 'visit_login.html')


# 访问统计页面(分页模板)
from app01 import models
from django.core.paginator import Paginator

def visit_data(request):
    # 🔰1. 分页后的 paginator 对象
    current_page = int(request.GET.get('page_num', 1))

    # 🔰2. 页码列表
    if paginator.num_pages > 9:
        if current_page - 4 < 1:
            page_range = range(1, 10)
        elif current_page + 4 > paginator.num_pages:
            page_range = range(paginator.num_pages - 8, paginator.num_pages + 1)
        else:
            page_range = range(current_page - 4, current_page + 4)
    else:
        page_range = paginator.page_range

    # 🔰3.page 对象
    try:
        page = paginator.page(current_page)
    except Exception as E:
        page = paginator.page(current_page)

    return render(request, 'visit_data.html', {'page_range': page_range, 'page': page, 'current_page': current_page})
  • 演示效果(没有做登入校验)

18 中间件与 CSRF 跨站请求伪造

二.CSRF 跨站请求伪造

1. 什么是 CSRF (简介)

CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack”或者 Session Riding,通常缩写为 CSRF 或者 XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与 XSS 非常不同,XSS 利用站点内的信任用户,而 CSRF 则通过伪装来自受信任用户的请求来利用受信任的网站。与 XSS 攻击相比,CSRF 攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比 XSS 更具危险性

  • 简单理解就是黑客盗用你的身份, 以你的身份来进行一系列操作(发送恶意请求, 发消息, 购物, 转账等等), 这些操作对于服务器来说是完全合法的, 因为你的身份就是一个正常的用户

2.CSRF 攻击原理示例

  • 以银行转账为例

18 中间件与 CSRF 跨站请求伪造

  • 正常用户登入受信任的网站 A, 并在本地生成 Cookie
  • 在没有登出网站 A 的情况下(也就是没有清除 Cookie), 访问了黑客网站 B
  • B 网站中有一个虚假按钮, 背后对应的就是向别人转账, 但用户并不知道, 点击之后会带着用户的 Cookie 进行转账

3.CSRF 攻击防范之 Referer

  • 验证 HTTP Referer 字段

在 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址, 服务器只要校验来源地址是不是自己的地址, 如果来源地址是其他网站的域名, 则很有可能是黑客的 CSRF 攻击, 直接拒绝该请求

  • 优缺点

优点 : 简单, 易于实现

缺点 : Referer 的值是浏览器提供的, 我们不能保证浏览器自身的漏洞, 这样依赖于第三方的校验显然是不安全的, 并且某些浏览器目前已经有一些方法可以篡改 Referer 值, 比如 IE6 或 FF2

4.CSRF 攻击防范之 token

  • 验证 token 随机字符串

在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求

三. 针对 from 表单 及 ajax 的 csrf_token 校验提交

1.CSRF 校验的中间件

  • 在 setting.py 中开启, 默认是打开的
'django.middleware.csrf.CsrfViewMiddleware',

2. 在前端进行请求

  • 如果没有携带 csrf 随机码, POST将会请求失败(403)

18 中间件与 CSRF 跨站请求伪造

3. 在页面 form 表单中加上 {% csrf_token %} (这是方法之一)

<form action="/csrf_test/" method="post">
    {% csrf_token %}
    ....
    ....
</form>

添加之后会在页面上生成一个隐藏的 input 标签, 里面的 name 对应 csrfmiddlewaretoken, value 对应一串随机码, 请求发送的时候就回去校验这个随机码

这个时候进行 POST 请求就可以成功了

<input type="hidden" name="csrfmiddlewaretoken" value="Fuih8hBFfSw7usOBVz1FF8yWYlBVeqmJ59O0HGGZp9Rko4Ovm9F5QnS2Zm0dKV5K">

4. 基于 from 表单提交 CSRF 请求

  • 实现的方法就是上面介绍的, 在 form 表单中添加 {% csrf_token %}

5. 基于 ajax 提交 CSRF 请求

  • 方式一 (1): 在 data 中放入 csrf 随机字符串
# 首先需要在 form 表单中写上 {% csrf_token %},(不然后面取不到)
# 然后直接通过 jQuery 语法获取到页面中隐藏的 input 框内的 name 和 value, 将其放入 data 中
data:{'csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]').val()}
  • 方式一 (2)
# 直接拿到 csrf_token 值进行渲染, 需要引号(不需要在 form 表单中写 csrf_token)
data:{'csrfmiddlewaretoken':'{{ csrf_token}}'}
  • 方式二(1、2) : 将其放在请求头中
# 需要在 form 表单中写上 {% csrf_token %}
headers:{'X-CSRFToken':$('[name="csrfmiddlewaretoken"]').val()}

# 或者直接使用 '{{csrf_token}}' 取值进行渲染
headers:{'X-CSRFToken':'{{ csrf_token}}'}

6.CSRF 校验示例

  • 路由层 urls.py 文件
# csrf 跨站请求伪造测试
re_path('^csrf_test/',views.csrf_test),
  • 视图层 views.py 文件
from django.shortcuts import render, HttpResponse, redirect

def csrf_test(request):
    if request.method == 'GET':
        return render(request,'csrf_test.html')
    elif request.method == "POST":
        from_name = request.POST.get('from_name')
        to_name = request.POST.get('to_name')
        money = request.POST.get('money')
        return HttpResponse(f'{from_name}向 {to_name} 转账 {money} 元成功')
  • 模板层 csrf_test.html 文件
<div class="container-fluid">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <div class="panel panel-default">
              <div class="panel-heading text-center">
                <h3 class="panel-title"> 转账 </h3>
              </div>
              <div class="panel-body">
                  <form action="/csrf_test/" method="post">
                      {% csrf_token %}  {# 方式一: 直接导入 csrf_token #}
                      <p> 你的账户名: <input type="text" name="from_name" class="form-control b1"></p>
                      <p> 对方账户名: <input type="text" name="to_name" class="form-control b2"></p>
                      <p> 转账金额: <input type="text" name="money" class="form-control b3"></p>
                      <input type="submit" value="form 转账 " class="btn btn-block btn-warning">
                  </form>
                  <br>
                  <input type="submit" class="btn btn-danger btn-block a1" value="Ajax 转账 ">
              </div>
            </div>
        </div>
    </div>
</div>

<script>
    $('.a1').click(function () {
        $.ajax({
            url:'/csrf_test/',
            method:'post',
            {# 方式二 : 请求体中放随机字符串, 两种方式放 #}
            {# 第一种: 直接通过页面获取 #}
            {#data:{'csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]').val(),'from_name':$('.b1').val(),'to_name':$('.b2').val(),'money':$('.b3').val()},#}
            {# 第二种 : 直接拿到随机字符串渲染好, 需要加引号 #}
            data:{'csrfmiddlewaretoken':'{{ csrf_token}}','from_name':$('.b1').val(),'to_name':$('.b2').val(),'money':$('.b3').val()},
            {# 方式三 : 在请求头上加 #}
            {#headers:{'X-CSRFToken':$('[name="csrfmiddlewaretoken"]').val()},#}
            success:function (data) {alert(data)
            }
        })
    })
</script>
  • 我们先不添加 csrf 来进行试验

18 中间件与 CSRF 跨站请求伪造

  • 添加 csrf 随机码来进行校验

18 中间件与 CSRF 跨站请求伪造

四.CSRF 校验局部禁用与局部使用

1. 两种装饰器

  • @csrf_exempt : 全局启用 CSRF 校验的时候, 使用该装饰器可以使得局部不进行校验
  • @csrf_protect : 全局禁用 CSRF 校验的时候, 使用该装饰器可以使得局部仍然进行校验

2. 两种装饰器的使用示例

  • 先导入装饰器
from django.views.decorators.csrf import csrf_exempt,csrf_protect
  • 为 CBV 添加装饰器
# @csrf_exempt  # 全局启用,csrf_CBV 可以不进行校验
@csrf_protect  # 全局禁用(也就是注释掉 CSRF 校验的中间件),csrf_CBV 仍然进行校验
def csrf_CBV(request):
    if request.method == 'GET':
        return render(request,'csrf_CBV.html')
    elif request.method == "POST":
        from_name = request.POST.get('from_name')
        to_name = request.POST.get('to_name')
        money = request.POST.get('money')
        return HttpResponse(f'{from_name}向 {to_name} 转账 {money} 元成功')
正文完
 
shawn
版权声明:本站原创文章,由 shawn 2023-06-16发表,共计8554字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)